package solver

import (
	"log"
)

const (
	basicLogFormat = "r%vc%v %v %v"
)

func (g *Grid) clearupInHouse(house House, solvedNum int) {
	value := house.cells[solvedNum].value
	for i, cell := range house.cells {
		if i == solvedNum || cell.solved() {
			continue
		}
		cell.removeCandidate(value)
	}
}

func (g *Grid) Clearup() {
	for rowNum := 0; rowNum < 9; rowNum++ {
		for colNum := 0; colNum < 9; colNum++ {
			if g.cells[rowNum][colNum].solved() {
				g.clearupInHouse(g.rows[rowNum], colNum)
				g.clearupInHouse(g.cols[colNum], rowNum)
				boxNum, cellNum := calcBoxAndCellNum(rowNum, colNum)
				g.clearupInHouse(g.boxes[boxNum], cellNum)
			}
		}
	}
}

func (g *Grid) LastOneHouse(house House) (value int, cellNum int) {
	if house.solvedCount != 8 {
		return
	}

	var cell *Cell
	for cellNum, cell = range house.cells {
		if cell.solved() {
			continue
		}
		for i, candidate := range cell.candidates {
			if !candidate {
				continue
			}
			value = i + 1
			return
		}
	}
	return
}

func (g *Grid) solveOneCell(rowNum int, colNum int, value int) {
	g.cells[rowNum][colNum].setValue(value)
	g.rows[rowNum].solvedCount += 1
	g.cols[colNum].solvedCount += 1
	boxNum, cellNum := calcBoxAndCellNum(rowNum, colNum)
	g.boxes[boxNum].solvedCount += 1
	g.solvedCount += 1
	g.clearupInHouse(g.rows[rowNum], colNum)
	g.clearupInHouse(g.cols[colNum], rowNum)
	g.clearupInHouse(g.boxes[boxNum], cellNum)
}

func (g *Grid) LastOne() (used bool) {
	for rowNum, row := range g.rows {
		value, colNum := g.LastOneHouse(row)
		if value != 0 {
			g.solveOneCell(rowNum, colNum, value)
			log.Printf(basicLogFormat, rowNum, colNum, value, "Row Lost One")
			return true
		}
	}
	for colNum, col := range g.cols {
		value, rowNum := g.LastOneHouse(col)
		if value != 0 {
			g.solveOneCell(rowNum, colNum, value)
			log.Printf(basicLogFormat, rowNum, colNum, value, "Col Lost One")
			return true
		}
	}
	for boxNum, box := range g.boxes {
		value, cellNum := g.LastOneHouse(box)
		if value != 0 {
			rowNum, colNum := calcRowAndColNum(boxNum, cellNum)
			g.solveOneCell(rowNum, colNum, value)
			log.Printf(basicLogFormat, rowNum, colNum, value, "Box Lost One")
			return true
		}
	}
	return false
}

func (g *Grid) HiddenSingleHouse(house House) (value int, cellNum int) {
	candCells, candFreqs := sumupCandCells(&house)
	for cand, freq := range candFreqs {
		if freq != 1 {
			continue
		}
		value = cand + 1
		for cellNum = 0; cellNum < 9; cellNum++ {
			if candCells[cand][cellNum] {
				return
			}
		}
	}
	return
}

func (g *Grid) HiddenSingleInRows() (used bool) {
	for rowNum, row := range g.rows {
		value, colNum := g.HiddenSingleHouse(row)
		if value != 0 {
			g.solveOneCell(rowNum, colNum, value)
			log.Printf(basicLogFormat, rowNum, colNum, value, "Row Hidden Single")
			return true
		}
	}
	return false
}

func (g *Grid) HiddenSingleInCols() (used bool) {
	for colNum, col := range g.cols {
		value, rowNum := g.HiddenSingleHouse(col)
		if value != 0 {
			g.solveOneCell(rowNum, colNum, value)
			log.Printf(basicLogFormat, rowNum, colNum, value, "Col Hidden Single")
			return true
		}
	}
	return false
}

func (g *Grid) HiddenSingleInBoxes() (used bool) {
	for boxNum, box := range g.boxes {
		value, cellNum := g.HiddenSingleHouse(box)
		if value != 0 {
			rowNum, colNum := calcRowAndColNum(boxNum, cellNum)
			g.solveOneCell(rowNum, colNum, value)
			log.Printf(basicLogFormat, rowNum, colNum, value, "Box Hidden Single")
			return true
		}
	}
	return false
}

func (g *Grid) NakedSingle() (used bool) {
	for rowNum, row := range g.rows {
		for colNum, cell := range row.cells {
			if !cell.solved() && cell.candidateCount == 1 {
				for i, candidate := range cell.candidates {
					if candidate {
						value := i + 1
						g.solveOneCell(rowNum, colNum, value)
						log.Printf(basicLogFormat, rowNum, colNum, value, "Naked Single")
						return true
					}
				}
			}
		}
	}
	return false
}
